package com.atwy.linearstructure.linkedList;

/**
 * 单向循环链表
 * 解决 Josepfu 问题
 * Josephu 问题为：
 * 设编号为 1，2，… n 的 n 个人围坐一圈，约定编号为 k（1<=k<=n）的人从 1 开始报数，
 * 数到 m 的那个人出列，它的下一位又从 1 开始报数，数到 m 的那个人又出列，
 * 依次类推，直到所有人出列为止，由此 产生一个出队编号的序列。
 */
public class CircleSingleLinkedListDemo {
    public static void main(String[] args) {
        CircleSingleLinkedList cslist = new CircleSingleLinkedList();
        cslist.addBoy(5);
        System.out.println("出圈之前->");
        cslist.showBoys();
        cslist.josepfu(5,2,2);

        System.out.println("出圈之后->");
        cslist.showBoys();

        System.out.println("========================");
        CircleSingleLinkedList cslist2 = new CircleSingleLinkedList();
        cslist2.addBoy(5);
        System.out.println("出圈之前->");
        cslist2.showBoys();
        cslist2.josepfu2(2,2);

        System.out.println("出圈之后->");
        cslist2.showBoys();

    }
}

/**
 * 构建单向环形链表
 */
class CircleSingleLinkedList {
    /**
     * 第一个节点
     */
    private Boy first = null;

    /**
     *
     * @param n 围成的圈一共有n个节点
     * @param k 编号为k的节点从1开始报数
     * @param m 数到m，出列（打印出来），最终打印出出圈顺序
     */
    public void josepfu(int n,int k,int m) {
        if(first == null || k > n || k < 1){
            System.out.println("参数错误");
            return;
        }
        // 找到第k个节点,问题：这里first如此使用，会导致first失去指向链表的意义
        Boy curr = first;
        Boy pre = curr;
        int total = 1;
        while (true){
            if(total == k){
                break;
            }
            total++;
            pre = curr;
            curr = curr.getNext();
        }
        // 出圈循环
        while (true){
            if(curr == null){
                break;
            }
            if(curr.getNo() == pre.getNo()){
                System.out.println(curr);
                // 清空链表
                first = null;
                break;
            }

            total = 1;
            while (true){
                if (total == m){
                    System.out.println(curr);
                    // 将当前节点从链表中去除
                    pre.setNext(curr.getNext());
                    curr = curr.getNext();
                    break;
                }
                total++;
                pre = curr;
                curr = curr.getNext();
            }
        }
    }

    /**
     * 约瑟夫问题的第二种方案
     * @param startNo 表示从第几个小孩开始数数
     * @param countNum 表示数几下
     */
    public void josepfu2(int startNo,int countNum){
        if(first == null || startNo < 1 ){
            System.out.println("参数错误");
            return;
        }
        // 构建辅助变量
        Boy helper = first;
        // helper事先应该指向环形链表的最后这个节点
        while (true){
            if(helper.getNext() == first){
                break;
            }
            helper = helper.getNext();
        }
        //小孩报数前， helper、first移动startNo-1，first指向 第startNo个小孩
        for (int i = 0; i < startNo - 1; i++) {
            first = first.getNext();
            helper = helper.getNext();
        }

        // 当小孩报数时，让 first 和 helper 指针同时 的移动 countNum - 1 次, 然后出圈
        while (true){
            if(helper == first){
                // 圈里只有最后一个小孩了
                break;
            }
            // 移动 countNum - 1 次
            for (int i = 0; i < countNum - 1; i++) {
                first = first.getNext();
                helper = helper.getNext();
            }
            //这时 first 指向的节点，就是要出圈的小孩节点
            System.out.println("当前出圈的小孩是："+first);
            helper.setNext(first.getNext());
            first = helper.getNext();
        }
        System.out.println("最后留在圈里的小孩是："+first);
    }

    /**
     * 添加节点，构建成环形
     *
     * @param nums
     */
    public void addBoy(int nums) {
        if (nums < 1) {
            System.out.println("添加数量错误");
            return;
        }
        // 辅助变量，帮助构建环形链表
        Boy currBoy = null;
        for (int i = 1; i <= nums; i++) {
            Boy boy = new Boy(i);
            if (i == 1) {
                first = boy;
                first.setNext(boy);
                // 此时指向第一个boy
                currBoy = first;
            } else {
                // 添加新boy，并让新boy与第一个连接在一起
                currBoy.setNext(boy);
                boy.setNext(first);
                // 辅助变量下移
                currBoy = boy;
            }
        }
    }

    /**
     * 展示当前链表中的节点
     */
    public void showBoys() {
        if (first == null) {
            System.out.println("当前链表没有节点");
            return;
        }
        // 辅助变量
        Boy currBoy = first;
        while (true) {
            System.out.printf("当前节点的编号 %d \n", currBoy.getNo());
            if (currBoy.getNext() == null || currBoy.getNext().getNo() == first.getNo()) {
                break;
            }
            // 辅助变量后移
            currBoy = currBoy.getNext();
        }
    }
}

/**
 * 节点的定义
 */
class Boy {
    /**
     * 编号
     */
    private int no;
    /**
     * 指向下一个节点
     */
    private Boy next;

    public Boy() {
    }

    public Boy(int no) {
        this.no = no;
    }

    public int getNo() {
        return no;
    }

    public void setNo(int no) {
        this.no = no;
    }

    public Boy getNext() {
        return next;
    }

    public void setNext(Boy next) {
        this.next = next;
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("Boy{");
        sb.append("no=").append(no);
        sb.append('}');
        return sb.toString();
    }
}

